#pragma once

#include <memory>
#include <string>
#include <vector>

#include "common/hash_util.h"
#include "planner/optimizer_defs.h"

extern "C" {
#include <nodes/pg_list.h>
}

namespace optimizer {

class OperatorVisitor;

class Operator {
 public:
  explicit Operator(OperatorType type) : type_(type) {}
  Operator(OperatorType type, Expression *expressions) : type_(type) { AddExpression(expressions); }
  Operator(OperatorType type, List *expressions) : type_(type), expressions_(expressions) {}

  Operator(OperatorType type, const std::shared_ptr<Operator> &child) : type_(type) { AddChild(child); }
  Operator(OperatorType type, const std::vector<std::shared_ptr<Operator>> &children)
      : type_(type), children_(children) {}

  virtual ~Operator() = default;

  virtual std::string ToString() const { return "Operator"; }

  virtual hash_t Hash() const;
  virtual hash_t HashWithChildren() const;

  virtual void Accept(OperatorVisitor *v);

  virtual std::shared_ptr<Operator> Copy() const;
  virtual std::shared_ptr<Operator> CopyWithChildren() const;

  virtual OperatorType GetOpType() const { return type_; };

  virtual bool IsLogical() const { return type_ < OperatorType::LOGICALPHYSICALDELIMITER; }

  virtual bool IsPhysical() const { return type_ > OperatorType::LOGICALPHYSICALDELIMITER; }

  virtual bool operator==(const Operator &r) const;

  virtual bool operator!=(const Operator &r) { return !(operator==(r)); }

  virtual void AddExpression(Expression *expr) {
    if (expr != nullptr)
      expressions_ = lappend(expressions_, expr);
  }

  virtual List *GetExpressions() const { return expressions_; }

  virtual void SetExpressions(List *exprs) { expressions_ = exprs; }

  virtual void AddChild(const std::shared_ptr<Operator> &child) { children_.emplace_back(child); }

  virtual const std::vector<std::shared_ptr<Operator>> &GetChildren() const { return children_; }

  virtual void SetChildren(const std::vector<std::shared_ptr<Operator>> &children) { children_ = children; }

  virtual void ReplaceChild(const std::shared_ptr<Operator> &new_op, uint32_t idx) { children_[idx] = new_op; }

  virtual void SetProject(List *project) { project_ = project; }

  virtual List *GetProject() const { return project_; }

  virtual void SetFilter(List *filter) { filter_ = filter; }

  virtual List *GetFilter() const { return filter_; }

  template <class TARGET>
  TARGET &CastTo() {
    if (TARGET::TYPE != OperatorType::UNDEFINED && TARGET::TYPE != type_) {
      throw std::runtime_error("Failed to cast logical operator to type - logical operator type mismatch");
    }
    return reinterpret_cast<TARGET &>(*this);
  }

  template <class TARGET>
  const TARGET &CastTo() const {
    if (TARGET::TYPE != OperatorType::UNDEFINED && TARGET::TYPE != type_) {
      throw std::runtime_error("Failed to cast logical operator to type - logical operator type mismatch");
    }
    return reinterpret_cast<const TARGET &>(*this);
  }

 protected:
  OperatorType type_;
  std::vector<std::shared_ptr<Operator>> children_;
  /* common expressions for logical operators, such as limit expr, sort expr, etc. */
  List *expressions_{nullptr};
  /* projection and filter, normalization set it */
  List *project_{nullptr};
  List *filter_{nullptr};
};

}  // namespace optimizer

namespace std {

template <>
struct hash<optimizer::Operator> {
  using argument_type = optimizer::Operator;

  using result_type = std::size_t;

  result_type operator()(argument_type const &s) const { return s.Hash(); }
};

}  // namespace std
